import opf.config as config
from opf.uri import URI
from opf.iso6391 import iso6391codes
from opf.container import Container
from opf.atomicpredicate import AtomicPredicate
from opf.predicate import Predicate
from opf.context import (setContext, getContext, 
    addInstance, addPredicate, Context)
from opf.exceptions import OPFError

class NotOntologicalParentClass(OPFError): pass
class DisjointError(OPFError): pass
class InstantiatingAbstractClass(OPFError): pass

if config.USE_WEAKREF:
    import weakref

def _remobj(classes):
    for cls in classes:
        if cls is not object: yield cls

def _predicate(func, start, stop):
    predicate = AtomicPredicate(func, start, stop)
    # in order to prevent predicate from garbage collection:
    addPredicate(predicate)
    return predicate

# TODO: use cardinalities
def _attribute(func, start, stop):
    attrname = "__opf_attr_" + func.__name__ + "__"
    def _set(itself, val):
        func(itself, val)
        setattr(itself, attrname, val)
    def _get(itself):
        return getattr(itself, attrname)
    rslt = property(fget = _get, fset = _set)
    return rslt

def CardinalifiedDecorator(f):
    class _Decorator:
        def __init__(self, start = 1, stop = 1):
            self.__start = start
            self.__stop = stop
        def __call__(self, func):
            return f(func, self.__start, self.__stop)
        def __getitem__(self, key):
            if isinstance(key, slice):
                start, stop = key.start, key.stop
            else: start, stop = key, key
            return _Decorator(start, stop)
    return _Decorator

PredicateDecorator = CardinalifiedDecorator(_predicate)
AttributeDecorator = CardinalifiedDecorator(_attribute)
attribute = AttributeDecorator()

class Topic(Context):
    def __init__(self, ontology):
        self.__ontology = ontology
    def _addInstance(self, inst):
        self.__ontology._addInstance(inst)
    def _addArc(self, arc):
        if len(arc) == 3: arc = self, arc[0], arc[1], arc[2]
        else:  arc = self, arc[1], arc[2], arc[3]
        self.__ontology._addArc(arc)
    def _addPredicate(self, p):
        self.__ontology._addPredicate(p)
    def _prohibitConsistencyChecks(self):
        return self.__ontology._prohibitConsistencyChecks()
    def __repr__(self):
        return "topic<%d>" % id(self)
    def __enter__(self):
        self.__prevContext = getContext()
        setContext(self)
    def __exit__(self, exc_type, exc_value, traceback):
        self.__ontology.graph.commit()
        setContext(self.__prevContext)

class Ontology(Context):
    def __init__(self, uri = 'http://www.example.com', **kwargs):
        self.__names = {}
        self.__classes = {}
        self.__arcs = Container()
        self.__disjoints = set()
        self.__predicates = set()
        self.__instances = {}
        self.__uriBase = uri
        self.__dirty = False
        for kw, val in kwargs.items():
            lname = iso6391codes.get(kw)
            if lname: self.__names[kw] = val
        self.__prevOntology = getContext()
        setContext(self)
    def _linkMembers(self, cls):
        for m in cls.__dict__.values():
            if hasattr(m, '__opf_kind__'):
                m.__opf_class__ = cls
                m.__opf_ontology__ = self
    def _checkAndRegisterClass(self, cls):
        cls.__opf_uri__ = URI(self.__uriBase, cls.__name__)
        def enumURIs(klass, uris):
            for base in _remobj(klass.__bases__):
                uris.add(base.__opf_uri__)
                enumURIs(base, uris)
        uris = set()
        enumURIs(cls, uris)
        uris = list(uris)
        for n, uri1 in enumerate(uris):
            uris1 = uris[n+1:]
            for uri2 in uris1:
                if frozenset((uri1, uri2)) in self.__disjoints:
                    raise DisjointError(definedType = str(cls.__opf_uri__),
                                        disjointSupertypes = (str(uri1),
                                                              str(uri2)))
        self._linkMembers(cls)
    if config.USE_WEAKREF:
        def _addInstance(self, inst):
            self.__instances[id(inst)] = weakref.ref(inst)
    else:
        def _addInstance(self, inst):
            self.__instances[id(inst)] = inst
    def _addArc(self, arc):
        self.__dirty = self.__arcs.add(arc) > 0
    def _addPredicate(self, p):
        self.__predicates.add(p)

    # Decorators:

    def _class(self, cls):
        '''
        Decorator for ontological classes
        '''
        def _init(itself, *args, **kwargs):
            oid = id(itself)
            if not oid in self.__instances:
                addInstance(itself)
            if hasattr(cls, "__opf_init__"):
                cls.__opf_init__(itself, *args, **kwargs)
        _init.__name__ = "__init__"
        if hasattr(cls, "__init__"):
            cls.__opf_init__ = cls.__init__
            cls.__init__ = _init
        for base in _remobj(cls.__bases__):
            if not hasattr(base, '__opf_uri__'):
                raise NotOntologicalParentClass(base.__name__)
        self._checkAndRegisterClass(cls)
        return cls
    def abstract(self, cls):
        '''
        Decorator for abstract base classes
        '''
        def _init(itself, *args, **kwargs):
            if hasattr(cls, "__opf_init__"):
                cls.__opf_init__(itself, *args, **kwargs)
            if itself.__class__.__name__ == cls.__name__:
                raise InstantiatingAbstractClass(cls)
        _init.__name__ = "__init__"
        if hasattr(cls, "__init__"):
            cls.__opf_init__ = cls.__init__
            cls.__init__ = _init
        for base in _remobj(cls.__bases__):
            if not hasattr(base, '__opf_uri__'):
                raise NotOntologicalParentClass(base.__name__)
        self._checkAndRegisterClass(cls)
        return cls
    def atomic(self, arg):
        '''
        Atomic predicate decorator
        '''
        rslt = PredicateDecorator()(arg)
        rslt.__opf_ontology__ = self
        return rslt
    def predicate(self, arg):
        '''
        Compound predicate decorator
        '''
        rslt = Predicate(arg)
        rslt.__opf_ontology__ = self
        return rslt
    def __call__(self, arg):
        '''
        Ontology itself can work as decorator
        '''
        if hasattr(arg, "__bases__"): return self._class(arg)
        elif hasattr(arg, "__call__"): return self.predicate(arg)
        return arg

    # Predicate functions

    def find(self, **kwargs):
        graph = self.__arcs
        predicate = kwargs.get("predicate"); del kwargs["predicate"]
        if isinstance(predicate, AtomicPredicate):
            arg = tuple(predicate.prepareArgs(**kwargs))
            return predicate.decodeQuadruples(graph[arg[0], predicate, arg[1]])
        elif isinstance(predicate, Predicate):
            return predicate.findInGraph(graph, **kwargs)
    def count(self, **kwargs):
        graph = self.__arcs
        predicate = kwargs.get("predicate"); del kwargs["predicate"]
        if isinstance(predicate, AtomicPredicate):
            arg = tuple(predicate.prepareArgs(**kwargs))
            return graph.count((arg[0], predicate, arg[1]))
        elif isinstance(predicate, Predicate):
            return len(list(predicate.findInGraph(graph, **kwargs)))
    def match(self, **kwargs):
        graph = self.__arcs
        predicate = kwargs.get("predicate"); del kwargs["predicate"]
        if isinstance(predicate, AtomicPredicate):
            arg = tuple(predicate.prepareArgs(**kwargs))
            return graph.count((arg[0], predicate, arg[1])) > 0
        elif isinstance(predicate, Predicate):
            result = list(predicate.findInGraph(graph, **kwargs))
            return bool(result)
    def refute(self, **kwargs):
        graph = self.__arcs
        predicate = kwargs.get("predicate"); del kwargs["predicate"]
        if isinstance(predicate, AtomicPredicate):
            arg = tuple(predicate.prepareArgs(**kwargs))
            del graph[(arg[0], predicate, arg[1])]
        elif isinstance(predicate, Predicate):
            # TODO: this function does not work since graph.delete doesn't
            predicate.deleteFromGraph(graph, **kwargs)
    
    # Other interface functions:

    @property
    def name(self): return self.__names
    if config.USE_WEAKREF:
        @property
        def instances(self):
            return set(x() for x in self.__instances.values())
    else:
        @property
        def instances(self):
            return set(self.__instances.values())
    @property
    def graph(self): return self.__arcs
    def __enter__(self):
        self.__prevOntology = getContext()
        setContext(self)
    def __exit__(self, exc_type, exc_value, traceback):
        self.__arcs.commit()
        setContext(self.__prevOntology)
    def disjoint(self, *args):
        for n, arg1 in enumerate(args):
            args1 = args[n+1:]
            for arg2 in args1:
                self.__disjoints.add(
                    frozenset((arg1.__opf_uri__, arg2.__opf_uri__)))
    def eraseGraph(self):
        self.__instances = {}
        self.__arcs.erase()
    def realize(self, *realizers):
        def _doRealize(r, args):
            nonlocal self
            for arg in args:
                for result in self.find(**arg): r(**result)
        def _arrangeRealizers(rlist):
            r, c = [], []
            for x in rlist:
                if hasattr(x, '__opf_realizer_num__'): r.append(x)
                else: c.append(x)
            for x in c:
                for y in dir(x):
                    y = getattr(x, y)
                    if hasattr(y, '__opf_realizer_num__'):
                        r.append(y)
            r.sort(key = lambda x: x.__opf_realizer_num__)
            return r
        while self.__dirty:
            self.__dirty = False
            for r in _arrangeRealizers(realizers):
                _doRealize(r, r.__opf_patterns__)
    def topic(self): return Topic(self)

_realizerNum = 0
def realizer(*args, **kwargs):
    if args: kwargs['predicate'] = args[0]
    def _decorate(func):
        nonlocal kwargs
        global _realizerNum
        patterns = getattr(func, '__opf_patterns__', [])
        patterns.append(dict(kwargs))
        func.__opf_patterns__ = patterns
        func.__opf_realizer_num__ = _realizerNum
        _realizerNum += 1
        return func
    return _decorate